/*
 * Copyright 2007 Stefan Arentz <http://stefan.arentz.nl>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sateh.paypal.impl;

import org.bouncycastle.cms.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.core.io.Resource;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.security.cert.CertStore;
import java.security.cert.CertificateFactory;
import java.security.cert.CollectionCertStoreParameters;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.net.URL;

import com.sateh.paypal.PayPalButtonGenerator;
import com.sateh.paypal.PayPalItem;
import com.sateh.paypal.PayPalButtonType;
import com.sateh.paypal.PayPalButtonGeneratorException;

public class PayPalButtonGeneratorImpl implements PayPalButtonGenerator, InitializingBean
{
    //
    // Public Bean Properties
    //

    private Resource payPalCertificate;
    private Resource businessCertificate;
    private String businessCertificateId;
    private Resource businessPrivateKey;
    private String businessPrivateKeyPassword;

    public Resource getPayPalCertificate()
    {
        return payPalCertificate;
    }

    public void setPayPalCertificate(Resource payPalCertificate)
    {
        this.payPalCertificate = payPalCertificate;
    }

    public Resource getBusinessCertificate()
    {
        return businessCertificate;
    }

    public void setBusinessCertificate(Resource businessCertificate)
    {
        this.businessCertificate = businessCertificate;
    }

    public String getBusinessCertificateId()
    {
        return businessCertificateId;
    }

    public void setBusinessCertificateId(String businessCertificateId)
    {
        this.businessCertificateId = businessCertificateId;
    }

    public Resource getBusinessPrivateKey()
    {
        return businessPrivateKey;
    }

    public void setBusinessPrivateKey(Resource businessPrivateKey)
    {
        this.businessPrivateKey = businessPrivateKey;
    }

    public String getBusinessPrivateKeyPassword()
    {
        return businessPrivateKeyPassword;
    }

    public void setBusinessPrivateKeyPassword(String businessPrivateKeyPassword)
    {
        this.businessPrivateKeyPassword = businessPrivateKeyPassword;
    }

    //
    // Private Implementation
    //

    private CertificateFactory certificateFactory;
    private KeyStore keyStore;

    X509Certificate businessX509Certificate;
    PrivateKey privateKey;
    X509Certificate payPalX509Certificate;

    public void afterPropertiesSet() throws Exception
    {
        Assert.notNull(payPalCertificate);
        Assert.notNull(businessCertificate);
        Assert.notNull(businessPrivateKey);
        Assert.notNull(businessPrivateKeyPassword);

        //
        // Add the BouncyCastleProvider
        //

        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        }

        keyStore = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
        certificateFactory = CertificateFactory.getInstance("X509", BouncyCastleProvider.PROVIDER_NAME);

        //
        // Read the Private Key
        //

        keyStore.load(businessPrivateKey.getInputStream(), businessPrivateKeyPassword.toCharArray());

        String keyAlias = null;
        Enumeration aliases = keyStore.aliases();
        while (aliases.hasMoreElements()) {
            keyAlias = (String) aliases.nextElement();
        }

        privateKey = (PrivateKey) keyStore.getKey(keyAlias, businessPrivateKeyPassword.toCharArray());

        //
        // Read the Certificates
        //

        businessX509Certificate = (X509Certificate) certificateFactory.generateCertificate(businessCertificate.getInputStream());
        payPalX509Certificate = (X509Certificate) certificateFactory.generateCertificate(payPalCertificate.getInputStream());
    }

    //

    private String encryptedButtonData(String command)
        throws GeneralSecurityException, CMSException, IOException
    {
        //
        // Sign
        //

        CMSSignedDataGenerator signedGenerator = new CMSSignedDataGenerator();
        signedGenerator.addSigner(privateKey, businessX509Certificate, CMSSignedDataGenerator.DIGEST_SHA1);

        ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>();
        certList.add(businessX509Certificate);

        CertStore certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList));
        signedGenerator.addCertificatesAndCRLs(certStore);

        CMSProcessableByteArray cmsByteArray = new CMSProcessableByteArray(command.getBytes());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        cmsByteArray.write(baos);

        CMSSignedData signedData = signedGenerator.generate(cmsByteArray, true, BouncyCastleProvider.PROVIDER_NAME);

        //
        // Encrypt
        //

        CMSEnvelopedDataGenerator envGenerator = new CMSEnvelopedDataGenerator();
        envGenerator.addKeyTransRecipient(payPalX509Certificate);

        CMSEnvelopedData envData = envGenerator.generate(new CMSProcessableByteArray(signedData.getEncoded()), CMSEnvelopedDataGenerator.DES_EDE3_CBC, BouncyCastleProvider.PROVIDER_NAME);

        //
        // Convert to PEM
        //

        String s = new String(Base64.encode(envData.getEncoded()));
        StringBuilder sb = new StringBuilder(s.length() + 64);
        sb.append("-----BEGIN PKCS7-----\n");
        for (int offset = 0; offset < s.length(); offset += 64) {
            sb.append(s.substring(offset, offset + 64 > s.length() ? s.length() : offset + 64));
            sb.append("\n");
        }
        sb.append("-----END PKCS7-----\n");

        return sb.toString();
     }    

    // Logic

    public String generateButton(String business, PayPalItem item, PayPalButtonType buttonType)
        throws PayPalButtonGeneratorException
    {
        return generateButton(business, item, buttonType.getUrl());
    }
    
    public String generateButton(String business, PayPalItem item, URL buttonUrl)
        throws PayPalButtonGeneratorException
    {
        try
        {
            StringBuilder commandBuilder = new StringBuilder();

            commandBuilder.append("cmd=_xclick\n");

            commandBuilder.append("business=");
            commandBuilder.append(business);
            commandBuilder.append("\n");

            commandBuilder.append("amount=");
            commandBuilder.append(item.getAmount().toString());
            commandBuilder.append("\n");

            commandBuilder.append("currency_code=");
            commandBuilder.append(item.getCurrency().getCurrencyCode());
            commandBuilder.append("\n");

            commandBuilder.append("item_name=");
            commandBuilder.append(item.getName());
            commandBuilder.append("\n");

            commandBuilder.append("item_number=");
            commandBuilder.append(item.getNumber());
            commandBuilder.append("\n");

            commandBuilder.append("cert_id=");
            commandBuilder.append(businessCertificateId);

            String encryptedCommand = encryptedButtonData(commandBuilder.toString());

            StringBuilder formBuilder = new StringBuilder();

            formBuilder.append("<form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\">\n");
            formBuilder.append("<input type=\"hidden\" name=\"cmd\" value=\"_s-xclick\">");
            formBuilder.append("<input type=\"image\" src=\"");
            formBuilder.append(buttonUrl.toExternalForm());
            formBuilder.append("\" border=\"0\" name=\"submit\">");
            formBuilder.append("<input type=\"hidden\" name=\"encrypted\" value=\"");
            formBuilder.append(encryptedCommand);
            formBuilder.append("\">");
            formBuilder.append("</form>\n");

            return formBuilder.toString();
        }

        catch (Exception e)
        {
            throw new PayPalButtonGeneratorException(e);
        }
    }
}
